<?php
/**
 * 路由器
 * @authors yutent (yutent@doui.cc)
 * @date    2015-07-08 15:15:52
 * 路由规则说明：
 * 必须以/开头，字母必须为全小写，支持3种规则方式:
 * 一：直接写path，如 /foo, /foo/bar ;
 * 二：使用“:id”匹配，":id"将等价于 [\w]+, 如 /:id,   /:id/bar
 * 三：使用“{id:}”匹配， "{id}" 等价于 ":id", 即 [\w]+, 如果需要更具体的匹配规则，只需要在冒号后面输写正则表达式即可，如：/{id:\d+},  /foo/{id:{3}}, /foo/{id:[a-z]{1,3}} ,以上3种规则分别等价于 /(\d+)/, /foo/(\w{3}), /foo/([a-z]{1,3}) 。需要注意的是，方式三不支持过于复杂的正则表达式
 * 
 * 范例：
 * ->get('/foo', function(){...})
 * ->get('/foo/bar', function(){...})
 * ->get('/:id', function($id){...})
 * ->get('/news/:id', function($sort){...})
 * ->get('/news/:id/{id:\d+}', function($sort, $id){...})
 * ->get('/news/:id/page/{id:[0-9]{1，3}}', function($sort, $page_id){...})
 */

class Do_Router {

    private static $router;
    private static $path;
    private static $init;
    
    function __construct(){

        $this->_get_path();
    }

    public function init($contrl = NULL){
        
        if(self::$init === NULL){
            self::$init = new Do_Router;
        }
        return self::$init;
    }

    /**
     * [uri 获取url的path部分, 并过滤掉除[A-z0-9_]以外的所有非法字符]
     * @return 
     */
    private function _get_path(){
        global $_G;
        $uri = strtolower(urldecode($_SERVER['REQUEST_URI']));
        $uri = trim(parse_url($uri, PHP_URL_PATH), '/');
        $uri = addslashes($uri);
        //过滤非法url字符
        $uri_fixed = preg_replace('/[^\w\/\.\-]+/', '', $uri);
        //过滤URL中可能出现的"多斜杠"，自动转换为"单斜杠"
        //如 abc.com/aa//index.html 自动转换为 abc.com/aa/index.html 
        $uri_fixed = preg_replace('/[\/]+/', '/', $uri_fixed);

        if($uri_fixed !== $uri){
            header('Location: http://' . $_G['domain'] . '/' . $uri_fixed);
            exit;
        }
        $uri_fixed = empty($uri_fixed) ? 'index' : $uri_fixed;
        $path_arr = explode('/', $uri_fixed);
        if(count($path_arr) === 1){
            $uri_fixed .= '/index';
        }
        self::$router = $path_arr[0];
        self::$path = $uri_fixed;
    }

    /**
     * [_get_regexp 私有方法，获取路由规则的正则表达式]
     * @param  string $rule [路由规则]
     * @return [type]       [正则表达式]
     */
    private function _get_regexp($rule = ''){
        if(empty($rule)) return NULL;

        $regexp = preg_replace_callback('/(:id)|(\{id\})|(\{id:([A-z\d\,\[\]\{\}\-\+\*\?\!:\^\$]*)\})/', function($matches){
            $w = '(\w';
            $i =  count($matches) - 1;
            if($matches[$i] === ':id' || $matches[$i] === '{id}'){
                return $w . '+)';
            }else{
                if(!preg_match('/^\{[\d\,]+\}$/', $matches[$i])){
                    $w = '(';
                }
                return $w . $matches[$i] . ')';
            }
        }, $rule);

        //过滤规则中可能出现的 多斜杠
        $regexp = preg_replace('/[\/]+/', '\/', $regexp);
        $regexp = preg_replace('/(([^\\\\])(\.+))/', '$2\.', $regexp);
        $regexp = preg_replace('/(([^\\\\])(\-+))/', '$2\-', $regexp);
        $regexp = preg_replace('/(\(.*)(\\\[\-]+)(.*\))/', '$1-$3', $regexp);

        return '/^' . $regexp . '$/';
    }

    /**
     * [_route 匹配路由规则,判断规则是否与URL相符]
     * @param  [type]  $rule      [路由规则]
     * @param  boolean $is_router [是否一级路由]
     * @return [type]             [匹配则返回数组结果，否则返回FALSE]
     */
    private function _route($rule, $is_router = FALSE){

        if(strpos($rule, '/') !== 0){
            D::log('[' . $rule . '] 路由规则必须以/开头');
            return;
        }

        $rule = trim($rule, '/');
        $regexp = $this->_get_regexp($rule);
        $path = $is_router ? self::$router : self::$path;

        preg_match($regexp, $path, $matches);

        
        if($matches){
            if(count($matches) > 1){
                array_shift($matches);
            }

            return $matches;
        }
        return FALSE;
    }

    /**
     * [route 一级路由，仅供入口控制器调用，apps里使用 下面的get方法]
     * @param  [type] $rule     [路由规则]
     * @param  [type] $callback [回调]
     */
    function route($rule, $callback = NULL){
        $route = $this->_route($rule, TRUE);
        if($route){
            if($callback){
                call_user_func_array($callback, $route);
            }
        }else{
            _404();
        }
    }

    /**
     * [get 二级路由， 仅在app里使用]
     * @param  [type] $rule     [路由规则]
     * @param  [type] $callback [回调]
     */
    function get($rule, $callback = NULL, $error = NULL){
        $route = $this->_route($rule);
        //非一级路由不会自动转到404页面
        if($route){
            if($callback){
                call_user_func_array($callback, $route);
            }
        }else{
            if($error){
                $error();
            }
        }
    }

}